%% TODO, should filter out directives
%% TODO, are things in the right order
%% TODO, why are parts of the compiler not deterministic?
%% TODO, maybe order things by arity more like KitchenSink.java

compile_file(SinkName) -->
 {write(parsing),nl},
  parse_file,
 {write(expanding),nl},
  expand_terms,
 {write(grouping),nl},
  group_relations,
 {write(simplifying),nl},
  simplify,
 {write(transfoming),nl},
  javafy(SinkName),
 {write('done!'),nl},
  save_output(SinkName).


expand_terms(RawTerms,Clauses) :- maplist(expand_ir_term,RawTerms,Clauses).
expand_ir_term(Term,Expansion) :-
 ir2pl(Term,PLTerm,_), '$my$expand_term'(PLTerm,PLExpansion), pl2ir(PLExpansion,Expansion).

group_relations([],[]).
group_relations([compound(':-',[_])|Rs],Gs) :- group_relations(Rs,Gs).
group_relations([R|Rs],[[R|G]|Gs]) :- simpler1(Name/Arity,R,_),
 select_group(Name/Arity,G,Rs,Rss), group_relations(Rss,Gs).
select_group(_,[],[],[]).
select_group(Name/Arity,Group,[G|Gs],Remain) :-
 ( simpler1(Name/Arity,G,_) ->
   Group=[G|Rest], Remain=RemainTail
 ; Group=Rest, Remain=[G|RemainTail]
 ),
 select_group(Name/Arity,Rest,Gs,RemainTail).

simplify(Groups,Simpler) :- maplist(simpler,Groups,Simpler).

javafy(SinkName,Rels,Java) :- javafy_relations(SinkName,Rels,Java,[]).

javafy_relations(SinkName,Relations) -->
 "public class ",SinkName," extends Sink {\n",
 "public ",SinkName,"(Sink next) { this.next = next; }\n",
 "public Stack<Choicepoint> fishFor(String name, int arity, Compound term, int choice, Prolog prolog, Unifier unifier, int cutIndex, Stack<Conjunct> continuation, Stack<Choicepoint> choicepoints) {\n",
 "Choicepoint choicepoint;\n",
 "int height = choicepoints.height;\n",
 javafy_relations(Relations).
javafy_relations([R|Rs]) --> java(R),javafy_relations(Rs).
javafy_relations([]) -->
 "return next.fishFor(name,arity,term,choice,prolog,unifier,cutIndex,continuation,choicepoints);\n",
 "}\n",
 "}\n".

save_output(SinkName,ProgramText,[]) :- !,
 append(SinkName,".java",Filename),
 open_file(Filename,write,S),
 write_string(S,ProgramText),
 close(S).



%% 

variables(V) --> { var(V) }, [V].
variables(C) --> { nonvar(C), C =.. [_|Args] }, list_variables(Args).
list_variables([])     --> [].
list_variables([T|Ts]) --> variables(T), list_variables(Ts).

namevars(Term,Ii,Io) :- variables(Term, Vars, []), namevars_in_list(Vars,Ii,Io).
namevars_in_list([],    E,E).
namevars_in_list([V|Vs],I,O) :- var(V), concat_atom(['V',I],V), succ(I,M), namevars_in_list(Vs,M,O).
namevars_in_list([A|Vs],I,O) :- nonvar(A), namevars_in_list(Vs,I,O).

pl2ir(PL,IR) :- copy_term(PL,PLCopy), pl2ir_aux(PLCopy, IR), namevars(IR,0,_).
pl2ir_aux(Variable, variable(Variable)) :- var(Variable).
pl2ir_aux(Compound, compound(Name,Args)) :-
 nonvar(Compound), Compound =.. [Name|PLArgs], maplist(pl2ir_aux,PLArgs,Args).

ir2pl(variable(Name),     Variable,Store) :- member(Name --> Variable,Store), !.
ir2pl(compound(Name,Args),Compound,Store) :- ir2pls(Args,ArgsPL,Store), Compound =.. [Name|ArgsPL].
ir2pls([],[],_).
ir2pls([I|Is],[P|Ps],Store) :- ir2pl(I,P,Store), ir2pls(Is,Ps,Store).
closed_list([]) :- !.
closed_list([_|Xs]) :- closed_list(Xs).



%% simpler(
%% [f(X,Y) :- a,b,c,
%%  f(p,q) :- z]
%%       ,
%% [[X,Y]-[a,b,c],
%%  [p,q]-[z]]).

simpler(Relation,relation(Name/Arity,Simpler)) :- maplist(simpler1(Name/Arity),Relation,Simpler).
simpler1(Name/Arity,compound(Name,Args),Args-[]) :- length(Args,Arity), Arity/Name \= 2/':-'.
simpler1(Name/Arity,compound(':-',[compound(Name,Args),Body]),Args-FlatBody) :- length(Args,Arity),
 flat_body(Body,FlatBody,[]).
flat_body(compound(',',[X,Y])) --> flat_body(X), flat_body(Y).
flat_body(T) --> [T].


java(variable(Name),I/Vs,X,I/Vs) --> {member(Name-X,Vs),!}.
java(variable(Name),I/Vs,I,O/[Name-I|Vs]) --> {atom_codes(I,Ic),atom_codes(Name,NameS),succ(I,O)},
 "Variable t",Ic," = new Variable(",string(NameS),");\n".
java(compound(Name,Args),I/Vi,I,O/Vo) --> {atom_codes(I,Ic),atom_codes(Name,NameS),succ(I,M)},
 splat(Args,Splatted,M/Vi,O/Vo),
 "Compound t",Ic," = new Compound(",string(NameS),tuple_variables(Splatted),");\n".

java(relation(Name/Arity,Clauses)) --> {atom_codes(Arity,ArityS),atom_codes(Name,NameS)},
 "if(arity == ",ArityS," && name.equals(",string(NameS),")) {\n",
 "switch(choice) {\n",
 cases(0,Name,Clauses),
 "}\n",
 "return choicepoints;\n",
 "}\n".

cases(_,_,[]) --> "default: choicepoints.head.dead = true;\n".
cases(I,Name,[Args-Body|Cases]) --> {succ(I,J),atom_codes(I,Is)},
 "case ",Is,": {\n",
 java(compound(Name,Args),0/[],_,State),
 splat(Body,Conjuncts,State,_),
 "if(unifier.unify(t0,term)) {\n",
 ({Cases = []} ->
   "choicepoints.head.query = choicepoints.head.query.tail;\n",
   ({Conjuncts = [ConjunctsH|ConjunctsT]} ->
     {atom_codes(ConjunctsH,ConjunctsHc)},
     "choicepoints.head.addQuerents(height-1, t",ConjunctsHc,tuple_variables(ConjunctsT),");\n"
   ; {true}),
   "choicepoints.head.addBindings(unifier.bound);\n",
   "return choicepoints;\n"
 ; "choicepoint = new Choicepoint(continuation,height",tuple_variables(Conjuncts),");\n",
   "choicepoint.bindings = unifier.bound;\n",
   "return new Stack<Choicepoint>(choicepoint,choicepoints);\n" ),
 "}\n",
 "break;\n",
 "}\n",
 cases(J,Name,Cases).

string(String) --> [0'"],string_body(String),!,[0'"].
string_body([0'"|Cs]) --> [0'\\],[0'"], string_body(Cs).
string_body([0'\\|Cs]) --> [0'\\],[0'\\], string_body(Cs).
string_body([C|Cs]) --> [C], string_body(Cs).
string_body([]) --> [].

splat([],[],E/Vs,E/Vs) --> [].
splat([T|Ts],[X|Is],I/Vi,O/Vo) --> java(T,I/Vi,X,M/Vm),splat(Ts,Is,M/Vm,O/Vo).
tuple_variables([]) --> [].
tuple_variables([I|Is]) --> {atom_codes(I,Ic)},", t",Ic,tuple_variables(Is).


%% Utility to print out terms as java
as_java(String) :- parse_string(String,Term), java(Term,0/[],_,_,Java,[]), write_string(Java),!.
